package de.gwtprime.server.model;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;

import de.gwtprime.server.sql.helper.HibernateHelper;
import de.gwtprime.shared.VoteType;

@Entity
@Table(name = "answer")
public class Answer extends EntityBaseVoteable{
	
	/**
	 * SerialVersionUID
	 */
	private static final long serialVersionUID = 1L;
	
	public Answer(){}
	
	private String text;
	
	private Set<VoteAnswer> votes = new HashSet<VoteAnswer>();
	
	private int voteValue;
	
	private Date datePosted = new Date();
	
	private Question question;
	
	private User2 user;
	
	private VoteAnswerComplete voteAnswerComplete;
	
	private String images;
	
	public Answer findAnswer(Long id){
		return HibernateHelper.find(Answer.class, id);
	}
	
	@Lob
	public synchronized String getText() {
		return text;
	}

	public synchronized void setText(String text) {
		this.text = text;
	}

	public synchronized Date getDatePosted() {
		return datePosted;
	}

	public synchronized void setDatePosted(Date datePosted) {
		this.datePosted = datePosted;
	}

	@ManyToOne
	@JoinColumn
	public synchronized Question getQuestion() {
		return question;
	}

	public synchronized void setQuestion(Question question) {
		this.question = question;
	}

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn
	public User2 getUser() {
		return user;
	}

	public void setUser(User2 user) {
		this.user = user;
	}
	
	/**
	 * Insert answer to DB and set relation to given quesiton id
	 * @param questionId
	 * TODO update 
	 */
	public Answer addToQuestion(Long questionId){
		EntityManager em = HibernateHelper.beginTransactionWithoutTransaction();
		try{
			
			// Get proxy -> no need to hit db, just to setup relation
			Question question = em.getReference(Question.class, questionId);
			
			this.setQuestion(question);
			
			persistPure(em);
			return this;

		}finally{
			em.close();
		}
	}
	
	
	@OneToMany(mappedBy = "answer")
	public Set<VoteAnswer> getVotes() {
		return votes;
	}

	public void setVotes(Set<VoteAnswer> votes) {
		this.votes = votes;
	}
	
	@Override
	public void decreaseVoteValueAndPersist(){
		decreaseVoteValue();
		persist();
	}
	
	
	public void addVote(VoteAnswer vote){
		EntityManager em = HibernateHelper.beginTransactionWithoutTransaction();

		try{
			User2 user = vote.getUser();

			if(user==null){
				// Not logged in users can't vote
				return;
			}

			if(getId()==null) persistPure(em);

			VoteAnswer voteFromDB = HibernateHelper.queryDB(new VoteAnswer(), String.format("from %s WHERE answer.id=%s AND user.id =%s", VoteAnswer.class.getSimpleName(), getId(), user.getId()));
			// if the user already votes -> just update the existing vote
			// with the new type (UP, DOWN)
			if(voteFromDB!=null){

				// if same type -> no update needed
				if(voteFromDB.getType()==vote.getType()){
					return;
				}

				// if vote is applied in opposite direction -> increase/decrease by 2 
				if(vote.getType()==VoteType.UP){
					voteValue = voteValue + 2;
				}else if(vote.getType()==VoteType.DOWN){
					voteValue = voteValue - 2;
				}
				voteFromDB.setType(vote.getType());
				voteFromDB.mergePure(em);
				return;
			}else{
				vote.setUser((User2)user.mergePure(em));
				vote.setAnswer(this);
				vote.persistPure(em);
			}


			if(votes.contains(vote)) return;
			votes.add(vote);

			if(vote.getType()==VoteType.DOWN)
				voteValue--;
			else if(vote.getType()==VoteType.UP)
				voteValue++;

		}finally{
			em.close();

		}
	}
	
	
	public void addVoteAndPersist(VoteAnswer vote, User2 user){
		vote.setUser(user);
		addVote(vote);
		persist();
	}
	
	public int getVoteValue() {
		return voteValue;
	}

	public void setVoteValue(int voteValue) {
		this.voteValue = voteValue;
	}
	
	public void addVoteAnswerComplete(VoteAnswerComplete vote){
		EntityManager em = HibernateHelper.beginTransactionWithoutTransaction();

		User2 user = vote.getUser();

		if(user==null){
			// Not logged in users can't vote
			return;
		}

		if(getId()==null) persistPure(em);

		
		//If already voted
		if(getVoteAnswerComplete()!=null){
			return;
			
		}else{
			vote.setUser((User2)user.mergePure(em));
			vote.setAnswer(this);
			vote.persistPure(em);
		}
		
		// Remove vote complete from other answers -> only one answer can complete a question
		if(getQuestion().isCompleted()){
			for(Answer ans : getQuestion().getAnswers()){
				if(ans.getVoteAnswerComplete()!=null){
					ans.getVoteAnswerComplete().remove();
					ans.setVoteAnswerComplete(null);
					ans.persist();
				}
			}
		}
		setVoteAnswerComplete(vote);
		
		// If the question is not already marked as completed -> Set the question to completed
		if(!getQuestion().isCompleted()){
			getQuestion().setCompleted(true);
			getQuestion().persist();
			
			try{
				Queue queue = QueueFactory.getDefaultQueue();
				queue.add(TaskOptions.Builder.withUrl("/karma").param("userId", user.getId().toString()));
			}catch(Exception e){
				logger.severe("Could not add Kamra Task to queue"+e);
			}
			
		}
		
		
		persist();
		em.close();
	}
	
	public void addVoteAnswerCompleteAndPersist(VoteAnswerComplete vote, User2 user){
		vote.setUser(user);
		addVoteAnswerComplete(vote);
		persist();
	}


	@OneToOne
	@JoinColumn
	public VoteAnswerComplete getVoteAnswerComplete() {
		return voteAnswerComplete;
	}


	public void setVoteAnswerComplete(VoteAnswerComplete voteAnswerComplete) {
		this.voteAnswerComplete = voteAnswerComplete;
	}

	@Lob
	public String getImages() {
		return images;
	}

	public void setImages(String images) {
		this.images = images;
	}

	
	
}
